package com.yds.ydsdatasetbackend.file.info;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.yds.ydsdatasetbackend.constant.FileConstant;
import com.yds.ydsdatasetbackend.exception.BusinessException;
import com.yds.ydsdatasetbackend.exception.ErrorCode;
import com.yds.ydsdatasetbackend.exception.ThrowUtils;
import com.yds.ydsdatasetbackend.file.RemoteInteraction;
import com.yds.ydsdatasetbackend.file.RemoteInteractionImp;
import com.yds.ydsdatasetbackend.model.dto.file.message.FileVariablesResponse;
import com.yds.ydsdatasetbackend.model.dto.file.message.GetFileMessageRequest;
import com.yds.ydsdatasetbackend.model.dto.file.message.GetFileMessageResponse;
import com.yds.ydsdatasetbackend.model.dto.file.predict.FilePredictInputParams;
import com.yds.ydsdatasetbackend.model.dto.file.predict.FilePredictOutputParams;
import com.yds.ydsdatasetbackend.model.dto.file.predict.common.PredictResult;
import com.yds.ydsdatasetbackend.model.dto.file.predict.remote.RemoteFilePredictAddRequest;
import com.yds.ydsdatasetbackend.model.dto.file.train.remote.RemoteFileTrainAddRequest;
import com.yds.ydsdatasetbackend.model.entity.File;
import com.yds.ydsdatasetbackend.model.entity.FilePredict;
import com.yds.ydsdatasetbackend.model.entity.FileTrain;
import com.yds.ydsdatasetbackend.model.entity.FileVariables;
import com.yds.ydsdatasetbackend.model.enums.FileFillEnum;
import com.yds.ydsdatasetbackend.model.enums.FileTrainEnum;
import com.yds.ydsdatasetbackend.model.vo.TrainResult;
import com.yds.ydsdatasetbackend.service.FilePredictService;
import com.yds.ydsdatasetbackend.service.FileService;
import com.yds.ydsdatasetbackend.service.FileTrainService;
import com.yds.ydsdatasetbackend.service.FileVariablesService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Slf4j
public class FileRemoteServiceImp implements FileRemoteService {

    @Resource
    private FileService fileService;

    @Resource
    private FileVariablesService fileVariablesService;

    @Resource
    private FileTrainService fileTrainService;

    @Resource
    private FilePredictService filePredictService;


    @Override
    public void getInfo(long fileId) {
        //1. 进行校验，防止未创建完成的数据重复进行
        File file = fileService.getById(fileId);
        if(file == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "文件不存在");
        }
        // 如果不是等待状态
        if(!file.getFillStatus().equals(FileFillEnum.WAITING.getValue())) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "文件正在被创建");
        }
        file.setFillStatus(FileFillEnum.RUNNING.getValue());
        boolean update = fileService.updateById(file);
        if(!update){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "文件错误");
        }

        // 2. 向python发出请求
        GetFileMessageRequest getFileMessageRequest = new GetFileMessageRequest();
        getFileMessageRequest.setUrl(FileConstant.COS_HOST + file.getUrl());

        RemoteInteraction remoteInteraction = new RemoteInteractionImp();
        GetFileMessageResponse getFileMessageResponse = remoteInteraction.executeCode(getFileMessageRequest);

        // 3.信息处理
        file.setFeatures(getFileMessageResponse.getFeatures());
        file.setInstances(getFileMessageResponse.getInstances());

        // 获取文件变量列表
        List<FileVariables> fileVariablesList = fileVariablesService.lambdaQuery()
                .eq(FileVariables::getFileId, fileId)
                .list();

        // 利用map构造映射，每个Column在一个文件中只有一个
        Map<Long, Long> resultMap = new HashMap<>();
        for (FileVariables fileVariables : fileVariablesList) {
            resultMap.put(fileVariables.getColumnNumber(), fileVariables.getId());
        }
        // 构造更新，创建列表
        List<FileVariables> createList = new ArrayList<>();
        List<FileVariables> updateList = new ArrayList<>();

        for (FileVariablesResponse item : getFileMessageResponse.getOutputList()) {
            FileVariables temp = new FileVariables();
            BeanUtils.copyProperties(item, temp);
            // 这一列存在过信息
            temp.setId(resultMap.getOrDefault(item.getColumnNumber(), null));
            temp.setFileId(fileId);
            if(temp.getId() == null){
                createList.add(temp);
            }else {
                updateList.add(temp);
            }


        }

        if(!createList.isEmpty()) {
            boolean save1 = fileVariablesService.saveBatch(createList,500);
            if(!save1) {
                file.setFillStatus(FileFillEnum.FAILED.getValue());
                fileService.updateById(file);
                throw new BusinessException(ErrorCode.SYSTEM_ERROR,"初始化参数失败");
            }
        }
        if(!updateList.isEmpty()) {
            boolean save2 = fileVariablesService.updateBatchById(updateList,500);
            if(!save2) {
                file.setFillStatus(FileFillEnum.FAILED.getValue());
                fileService.updateById(file);
                throw new BusinessException(ErrorCode.SYSTEM_ERROR,"初始化参数失败");
            }
        }

        // 4. 存入数据库
        file.setFillStatus(FileFillEnum.SUCCEED.getValue());
        boolean b = fileService.updateById(file);
        ThrowUtils.throwIf(!b, ErrorCode.SYSTEM_ERROR);

    }

    @Override
    public void doTrain(long fileTrainId) {
        //1. 进行校验，防止未创建完成的数据重复进行
        FileTrain fileTrain = fileTrainService.getById(fileTrainId);
        if(fileTrain == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "训练信息不存在");
        }
        // 如果不是等待状态
        if(!fileTrain.getTrainStatus().equals(FileTrainEnum.WAITING.getValue())) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "训练文件正在被创建");
        }
        fileTrain.setTrainStatus(FileFillEnum.RUNNING.getValue());
        boolean update = fileTrainService.updateById(fileTrain);
        if(!update){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "文件错误");
        }

        // 2. 构造参数信息，并向python发出请求
        File file = fileService.getById(fileTrain.getFileId());

        RemoteFileTrainAddRequest remoteFileTrainAddRequest = new RemoteFileTrainAddRequest();
        remoteFileTrainAddRequest.setId(fileTrain.getId());
        remoteFileTrainAddRequest.setUrl(FileConstant.COS_HOST + file.getUrl());
        remoteFileTrainAddRequest.setModel(fileTrain.getModel());
        remoteFileTrainAddRequest.setTrainRate(fileTrain.getTrainRate());
        remoteFileTrainAddRequest.setIsRefresh(fileTrain.getIsRefresh());
        remoteFileTrainAddRequest.setSelectFeatures(JSONUtil.toList(fileTrain.getSelectFeatures(), Long.class));
        remoteFileTrainAddRequest.setSelectTargets(JSONUtil.toList(fileTrain.getSelectTargets(), Long.class));
        remoteFileTrainAddRequest.setFileId(fileTrain.getFileId());
        remoteFileTrainAddRequest.setUserId(fileTrain.getUserId());

        // 3.向python端发送请求
        RemoteInteraction remoteInteraction = new RemoteInteractionImp();
        String responseStr = remoteInteraction.doTrain(remoteFileTrainAddRequest);
        if(StringUtils.isBlank(responseStr)) {
            fileTrain.setTrainStatus(FileTrainEnum.FAILED.getValue());
            fileTrainService.updateById(fileTrain);
            throw new BusinessException(ErrorCode.API_REQUEST_ERROR, "doTrain remoteTrain error message = {}" + responseStr);
        }
        TrainResult trainResult = JSONUtil.toBean(responseStr, TrainResult.class);
        // 4.处理请求信息
        fileTrain.setTrainResult(JSONUtil.toJsonStr(trainResult));
        fileTrain.setTrainStatus(FileTrainEnum.SUCCEED.getValue());
        boolean updated = fileTrainService.updateById(fileTrain);
        ThrowUtils.throwIf(!updated, ErrorCode.SYSTEM_ERROR);
    }

    @Override
    public void doPredict(long predictId) {
        FilePredict filePredict = filePredictService.getById(predictId);
        if(filePredict == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "预测信息不存在");
        }
        // 如果不是等待状态
        if(!filePredict.getPredictStatus().equals(FileTrainEnum.WAITING.getValue())) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "训练文件正在被创建");
        }
        filePredict.setPredictStatus(FileFillEnum.RUNNING.getValue());
        boolean update = filePredictService.updateById(filePredict);
        if(!update){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "文件错误");
        }

        FileTrain fileTrain = fileTrainService.getById(filePredict.getTrainId());


        List<FilePredictInputParams> predictParams = JSONUtil.toList(filePredict.getPredictParams(), FilePredictInputParams.class);
        List<Double> paramList = predictParams.stream().map(FilePredictInputParams::getParam).collect(Collectors.toList());

        // 构造请求参数
        RemoteFilePredictAddRequest remoteFilePredictAddRequest = new RemoteFilePredictAddRequest();
        remoteFilePredictAddRequest.setUserId(fileTrain.getUserId());
        remoteFilePredictAddRequest.setFileId(fileTrain.getFileId());
        remoteFilePredictAddRequest.setTrainId(fileTrain.getId());
        remoteFilePredictAddRequest.setModel(fileTrain.getModel());
        remoteFilePredictAddRequest.setParams(paramList);

        // 向python端发送请求
        RemoteInteraction remoteInteraction = new RemoteInteractionImp();
        String responseStr = remoteInteraction.doPredict(remoteFilePredictAddRequest);

        // 处理响应数据
        if(StringUtils.isBlank(responseStr)) {
            filePredict.setPredictStatus(FileTrainEnum.FAILED.getValue());
            filePredictService.updateById(filePredict);
            throw new BusinessException(ErrorCode.API_REQUEST_ERROR, "doPredict remoteTrain error message = {}" + responseStr);
        }

        PredictResult predictResult = JSONUtil.toBean(responseStr, PredictResult.class);
        if(predictResult.getCode() != 0) {
            log.info("predictCode:" + predictResult.getMessage());
            filePredict.setPredictStatus(FileTrainEnum.FAILED.getValue());
            filePredict.setPredictResult(JSONUtil.toJsonStr(predictResult));
            boolean updated = filePredictService.updateById(filePredict);
            ThrowUtils.throwIf(!updated, ErrorCode.SYSTEM_ERROR);
        } else{
            filePredict.setPredictStatus(FileTrainEnum.SUCCEED.getValue());
            filePredict.setPredictResult(JSONUtil.toJsonStr(predictResult));
            boolean updated = filePredictService.updateById(filePredict);
            ThrowUtils.throwIf(!updated, ErrorCode.SYSTEM_ERROR);
        }

    }

}
